/* 2014/8/22 */
package com.cosmos.quartz.control.xml;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;

import org.apache.log4j.Logger;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;

import com.cosmos.quartz.control.AgentControlIgnore;
import com.cosmos.quartz.control.ControlRequest;
import com.cosmos.quartz.control.ControlResponse;
import com.cosmos.quartz.control.QuartzController;
import com.thoughtworks.xstream.XStream;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@AgentControlIgnore
public class XmlQuartzControlJob implements Job {

	private static final Logger log = Logger.getLogger(XmlQuartzControlJob.class.getSimpleName());

	//private static DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault());
	
	private static final XStream xStream;
	
	private static final String ACCEPTED_FILE_MARK = "#";
	
	private static final String REJECTED_FILE_MARK = "!";
	
	private static final String RESPONSE_FILENAME_SEPARATOR = "_";
	
	private static final String RESPONSE_FILENAME_FORMAT = "%s_%s.xml";
	
	private static final String AGENT_NAME;
	
	private static final String REQUEST_DIR_PATH;
	
	private static final String RESPONSE_DIR_PATH;
	
	private static final Set<String> PROCESSED_REQUEST_SET; 
	
	static {
		xStream = new XStream();
		xStream.processAnnotations(XmlControlRequest.class);
		xStream.processAnnotations(XmlControlResponse.class);

		String packageName = XmlQuartzControlJob.class.getPackage().getName();
		
		String baseName = getStringFromSystemProperty(packageName +  ".properties", "control");	
		ResourceBundle bundle = null;
		try {						
			bundle = ResourceBundle.getBundle(baseName);			
		} catch (MissingResourceException ex) {
			log.error("Get resouce bundle failed.", ex);
		}		

		if (bundle != null) {
			AGENT_NAME = getStringFromResourceBundle(bundle, "AgentName", "Agent");
			REQUEST_DIR_PATH = getStringFromResourceBundle(bundle, "RequestDir", ".");
			RESPONSE_DIR_PATH = getStringFromResourceBundle(bundle, "ResponseDir", ".");
		} else {
			AGENT_NAME = getStringFromSystemProperty(packageName + ".agentName", "Agent");
			REQUEST_DIR_PATH = getStringFromSystemProperty(packageName + ".requestDir", ".");
			RESPONSE_DIR_PATH = getStringFromSystemProperty(packageName + ".responseDir", ".");
		}
		
		PROCESSED_REQUEST_SET = new HashSet<>();
		checkResponse();
	}
	
	private static String getStringFromResourceBundle(ResourceBundle bundle, String key, String defaultValue) {
		String value = defaultValue;
		if (bundle != null) {
			try {
				value = bundle.getString(key);
			} catch (MissingResourceException ex) {
				log.error("Get string from resource bundle failed: " + key, ex);
				log.info("Use default value: " + defaultValue);
			}	
		}
		return value;
	}
	
	private static String getStringFromSystemProperty(String key, String defaultValue) {
		String value = System.getProperty(key);
		if (value == null) {
			log.error("Get string from system property failed: " + key);
			
			value = defaultValue;
			log.info("Use default value: " + defaultValue);
		}
		return value;
	}
	
	/**
	 * List response directory, add all processed request file name to set.
	 * All file names in the set will be ignored in later process.
	 */
	private static void checkResponse() {
		File responseDir = new File(RESPONSE_DIR_PATH);
		if (!responseDir.exists()) {
			log.error(String.format("Response file directory \"%s\" is not existed.", RESPONSE_DIR_PATH));
			return;
		}
		
		File[] responseFiles = responseDir.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				return name.startsWith(AGENT_NAME);
			}
		});
		
		for (File responseFile : responseFiles) {
			String fileName = responseFile.getName();
			int startIndex = fileName.indexOf(RESPONSE_FILENAME_SEPARATOR);
			int endIndex = fileName.lastIndexOf(".");
			
			PROCESSED_REQUEST_SET.add(fileName.substring(startIndex + 1, endIndex));
		}
	}
	
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		log.info("Receive requests");		
		XmlControlRequest[] requests = receiveRequest();
		if (requests == null) {
			return;
		}
		
		QuartzController controller = new QuartzController();
		List<XmlControlResponse> responseList = new ArrayList<>(requests.length);
		for (XmlControlRequest request : requests) {
			
			ControlRequest baseRequest = request.toBaseRequest();
			ControlResponse baseResponse = controller.process(baseRequest); 
			XmlControlResponse response = new XmlControlResponse(baseResponse);
			response.setRequest(request);
			
			/*if (response.isSucceed()) {
				acceptRequest(request);
			} else {
				rejectRequest(request);
			}*/
			PROCESSED_REQUEST_SET.add(getFileNameWithoutExtension(request.getFilePath()));
			
			responseList.add(response);
		}
		
		log.info("Send responses.");
		XmlControlResponse[] responses = responseList.toArray(new XmlControlResponse[0]);
		sendResponse(responses);
	}
	
	protected XmlControlRequest[] receiveRequest() {
		File requestDir = new File(REQUEST_DIR_PATH);
		if (!requestDir.exists()) {
			log.error(String.format("Request file directory \"%s\" is not existed.", REQUEST_DIR_PATH));
			return null;
		}
		
		File[] requestFiles = requestDir.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				// Ignore files which are not XML.
				if (!name.endsWith(".xml") && !name.endsWith(".XML")) {
					return false;
				}
				
				// Ignore processed files.
				/*if (name.startsWith(ACCEPTED_FILE_MARK) ||
						name.startsWith(REJECTED_FILE_MARK)) {
					return false;
				}*/
				
				if (PROCESSED_REQUEST_SET.contains(name)) {
					return false;
				}
				
				return true;
			}
		});
		
		List<XmlControlRequest> requestList = new ArrayList<>();
		for (File requestFile : requestFiles) {
			Reader reader = null;
			InputStream input = null;
			try {
				input = new FileInputStream(requestFile);
				reader = new InputStreamReader(input, "UTF-8");
				XmlControlRequest request = (XmlControlRequest)xStream.fromXML(requestFile);
				request.setFilePath(requestFile.getName());
				requestList.add(request);
			} catch (Exception ex) {
				log.error("Read request file failed.", ex);
			} finally {
				close(reader);
				close(input);
			}
		}
		
		XmlControlRequest[] requests = requestList.toArray(new XmlControlRequest[0]);
		return requests;
	}
	
	protected void acceptRequest(XmlControlRequest request) {
		try {
			File requestFile = new File(request.getFilePath());
			File acceptedFile = new File(requestFile.getParent(), ACCEPTED_FILE_MARK + requestFile.getName());
			requestFile.renameTo(acceptedFile);
		} catch (Exception ex) {
			log.error("Rename request file failed.", ex);
		}
	}
	
	protected void rejectRequest(XmlControlRequest request) {
		try {
			File requestFile = new File(request.getFilePath());
			File rejectedFile = new File(requestFile.getParent(), REJECTED_FILE_MARK + requestFile.getName());
			requestFile.renameTo(rejectedFile);
		} catch (Exception ex) {
			log.error("Rename request file failed.", ex);
		}
	}

	protected void sendResponse(XmlControlResponse[] responses) {
		if (responses == null) {
			return;
		}
		
		File responseDir = new File(RESPONSE_DIR_PATH);
		if (!responseDir.exists()) {
			log.error(String.format("Response file directory \"%s\" is not existed.", RESPONSE_DIR_PATH));
			return;
		}
		
		for (XmlControlResponse response : responses) {
			//Date now = new Date();
			String fileName = String.format(RESPONSE_FILENAME_FORMAT, AGENT_NAME, 
					getFileNameWithoutExtension(response.getRequest().getFilePath()));
			File responseFile = new File(RESPONSE_DIR_PATH, fileName);
			Writer writer = null;
			OutputStream output = null;
			try {
				output = new FileOutputStream(responseFile);
				writer = new OutputStreamWriter(output, "UTF-8");
				xStream.toXML(response, writer);
			} catch (Exception ex) {
				log.error("Write response file failed.", ex);
			} finally {
				close(writer);
				close(output);
			}
		}
	}
	
	private String getFileNameWithoutExtension(String fileName) {
		int extensionIndex = fileName.lastIndexOf(".");
		if (extensionIndex == -1) {
			return fileName;
		} else {
			return fileName.substring(0, extensionIndex);
		}
	}

	private void close(Closeable resource) {
		if (resource != null) {
			try {
				resource.close();
			} catch (IOException ex) {
				log.error("Close resource failed.", ex);
			}	
		}
	}
	
}
